package biolabs.query;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import biolabs.core.Attribute;
import biolabs.core.BioObject;

/**
 * Expression that evaluates to the value of a bio-object's attribute.
 *
 * @author Tomas Mikula
 */
public class AttributeExpression implements Expression {

	private final String aName;

	public AttributeExpression(String attributeName) {
		if(attributeName == null)
			throw new NullPointerException("Attribute name cannot be null.");
		this.aName = attributeName;
	}

	@Override
	public Object eval(BioObject obj) {
		Class<? extends BioObject> clazz = obj.getClass();
		for(Field f: clazz.getFields()) {
			if(!Modifier.isPublic(f.getModifiers())) {
				// Attribute must be public.
				// This should have been reported by
				// the annotation processor => ignore.
				continue;
			}
			Attribute a = f.getAnnotation(Attribute.class);
			if(a == null)
				continue;
			String name;
			if(a.name().equals("")) {
				name = f.getName();
			} else {
				name = a.name();
			}
			if(name.equals(aName))
				try {
					return f.get(obj);
				} catch (IllegalAccessException e) {
					throw new RuntimeException(clazz.getName() + '#' + f.getName() + " inaccessible due to access control restrictions.");
				}
		}
		for(Method m: clazz.getMethods()) {
			if(!Modifier.isPublic(m.getModifiers())) {
				// Attribute must be public.
				// This should have been reported by
				// the annotation processor => ignore.
				continue;
			}
			Attribute a = m.getAnnotation(Attribute.class);
			if(a == null)
				continue;

			// check if m has a correct signature for an attribute getter
			if(m.getReturnType() == Void.class) {
				// m returns void => m is probably the setter
				continue;
			}
			if(m.getParameterTypes().length > 0 || m.getExceptionTypes().length > 0) {
				// Getter cannot take any arguments or throw exceptions.
				// This should have been reported by
				// the annotation processor => ignore.
				continue;
			}

			String name;
			if(a.name().equals("")) {
				name = m.getName();
				if(name.length() <= 3 || !name.substring(0, 3).equals("get")) {
					// Illegal attribute getter name.
					// This should have been reported by
					// the annotation processor => ignore.
					continue;
				}
				name = Character.toLowerCase(name.charAt(3)) + name.substring(4);
			} else {
				name = a.name();
			}
			if(name.equals(aName))
				try {
					return m.invoke(obj);
				} catch (IllegalAccessException e) {
					throw new RuntimeException(clazz.getName() + '#' + m.getName() + " inaccessible due to access control restrictions.");
				} catch (InvocationTargetException e) {
					throw new RuntimeException("Attribute getter " + clazz.getName() + '#' + m.getName() + " threw an exception.", e.getCause());
				}
		}
		return null;
	}

}